package com.docmgmt.mvp.ragflow;

import com.docmgmt.mvp.config.RAGFlowConfig;
import com.docmgmt.mvp.exception.BusinessException;
import com.docmgmt.mvp.ragflow.dto.ChatAssistantDetailVO;
import com.docmgmt.mvp.ragflow.dto.ChunkDetailVO;
import com.docmgmt.mvp.ragflow.dto.ChunkRetrievalRequest;
import com.docmgmt.mvp.ragflow.dto.ChunkRetrievalResultVO;
import com.docmgmt.mvp.ragflow.dto.CreateChatAssistantRequest;
import com.docmgmt.mvp.ragflow.dto.EmbeddingModelVO;
import com.docmgmt.mvp.ragflow.dto.KnowledgeBaseDetailVO;
import com.docmgmt.mvp.ragflow.dto.UpdateChatAssistantRequest;
import com.docmgmt.mvp.ragflow.dto.UpdateChunkRequest;
import com.docmgmt.mvp.ragflow.dto.UpdateDocumentRequest;
import com.docmgmt.mvp.ragflow.dto.UpdateKnowledgeBaseRequest;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;

import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * RAGFlow 客户端
 *
 * 封装 RAGFlow REST API 调用
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RAGFlowClient {

    private final RAGFlowConfig config;
    private final ObjectMapper objectMapper;

    /**
     * 创建 WebClient（带认证头）
     */
    private WebClient createWebClient() {
        return WebClient.builder()
                .baseUrl(config.getBaseUrl())
                .defaultHeader("Authorization", "Bearer " + config.getApiKey())
                .defaultHeader("Content-Type", "application/json")
                .build();
    }

    /**
     * 创建知识库
     *
     * POST /api/v1/datasets
     */
    public KnowledgeBaseData createKnowledgeBase(CreateKnowledgeBaseRequest request) {
        log.info("创建 RAGFlow 知识库: {}", request.getName());

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<KnowledgeBaseData> response = client.post()
                    .uri("/api/v1/datasets")
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<KnowledgeBaseData>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("创建知识库失败: " + errorMsg);
            }

            log.info("知识库创建成功: id={}, name={}", response.getData().getId(), request.getName());
            return response.getData();

        } catch (Exception e) {
            log.error("创建知识库失败", e);
            throw new BusinessException("创建知识库失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取知识库列表
     *
     * GET /api/v1/datasets
     */
    public List<KnowledgeBaseData> listKnowledgeBases() {
        log.info("获取知识库列表");

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<List<KnowledgeBaseData>> response = client.get()
                    .uri("/api/v1/datasets")
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<KnowledgeBaseData>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("获取知识库列表失败: " + errorMsg);
            }

            log.info("获取到 {} 个知识库", response.getData().size());
            return response.getData();

        } catch (Exception e) {
            log.error("获取知识库列表失败", e);
            throw new BusinessException("获取知识库列表失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取知识库详情
     *
     * GET /api/v1/datasets?id={dataset_id}
     *
     * 注意：RAGFlow SDK API 没有提供 GET /api/v1/datasets/{dataset_id} 端点，
     * 所以我们使用 GET /api/v1/datasets?id={dataset_id} 来获取单个知识库详情。
     *
     * @param datasetId 知识库ID
     * @return 知识库详情
     */
    public KnowledgeBaseDetailVO getKnowledgeBaseDetail(String datasetId) {
        log.info("获取知识库详情: dataset_id={}", datasetId);

        try {
            WebClient client = createWebClient();

            // 使用 /api/v1/datasets?id={dataset_id} 获取单个知识库
            RAGFlowResponse<List<KnowledgeBaseData>> response = client.get()
                    .uri(uriBuilder -> uriBuilder
                            .path("/api/v1/datasets")
                            .queryParam("id", datasetId)
                            .build())
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<KnowledgeBaseData>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("获取知识库详情失败: " + errorMsg);
            }

            List<KnowledgeBaseData> datasets = response.getData();
            if (datasets == null || datasets.isEmpty()) {
                throw new BusinessException("知识库不存在: " + datasetId);
            }

            // 将 KnowledgeBaseData 转换为 KnowledgeBaseDetailVO
            KnowledgeBaseData data = datasets.get(0);
            KnowledgeBaseDetailVO detail = KnowledgeBaseDetailVO.builder()
                    .id(data.getId())
                    .name(data.getName())
                    .description(data.getDescription())
                    .embeddingModel(data.getEmbeddingModel())
                    .documentCount(data.getDocNum())
                    .chunkCount(data.getChunkNum())
                    .createdAt(data.getCreateTime())
                    .updatedAt(data.getUpdateTime())
                    .language(data.getLanguage())
                    .build();

            log.info("获取知识库详情成功: id={}, name={}", detail.getId(), detail.getName());
            return detail;

        } catch (Exception e) {
            log.error("获取知识库详情失败: dataset_id={}", datasetId, e);
            throw new BusinessException("获取知识库详情失败: " + e.getMessage(), e);
        }
    }

    /**
     * 更新知识库配置
     *
     * PUT /api/v1/datasets/{dataset_id}
     *
     * @param datasetId 知识库ID
     * @param request   更新请求
     * @return 更新后的知识库详情
     */
    public KnowledgeBaseDetailVO updateKnowledgeBase(String datasetId, UpdateKnowledgeBaseRequest request) {
        log.info("更新知识库配置: dataset_id={}, name={}", datasetId, request.getName());

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<KnowledgeBaseDetailVO> response = client.put()
                    .uri("/api/v1/datasets/{id}", datasetId)
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<KnowledgeBaseDetailVO>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("更新知识库配置失败: " + errorMsg);
            }

            log.info("知识库配置更新成功: id={}, name={}", response.getData().getId(), request.getName());
            return response.getData();

        } catch (Exception e) {
            log.error("更新知识库配置失败: dataset_id={}", datasetId, e);
            throw new BusinessException("更新知识库配置失败: " + e.getMessage(), e);
        }
    }

    /**
     * 删除知识库
     *
     * DELETE /api/v1/datasets
     *
     * @param datasetIds 知识库ID列表
     */
    public void deleteKnowledgeBases(List<String> datasetIds) {
        log.info("删除知识库: ids={}", datasetIds);

        try {
            WebClient client = createWebClient();

            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("ids", datasetIds);

            // WebClient的DELETE请求需要使用method()方法来携带请求体
            RAGFlowResponse<Void> response = client.method(HttpMethod.DELETE)
                    .uri("/api/v1/datasets")
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<Void>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("删除知识库失败: " + errorMsg);
            }

            log.info("知识库删除成功: ids={}", datasetIds);

        } catch (Exception e) {
            log.error("删除知识库失败: ids={}", datasetIds, e);
            throw new BusinessException("删除知识库失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取所有可用的 Embedding 模型列表
     *
     * GET /v1/llm/list?model_type=embedding
     *
     * 注意：这是Web UI API，不是SDK API
     *
     * @return Embedding 模型列表
     */
    public List<EmbeddingModelVO> listEmbeddingModels() {
        log.info("获取 Embedding 模型列表");

        try {
            WebClient client = createWebClient();

            // 调用Web UI API获取embedding模型列表
            RAGFlowResponse<Map<String, List<Map<String, Object>>>> response = client.get()
                    .uri(uriBuilder -> uriBuilder
                            .path("/v1/llm/list")
                            .queryParam("model_type", "embedding")
                            .build())
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<Map<String, List<Map<String, Object>>>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("获取 Embedding 模型列表失败: " + errorMsg);
            }

            // 解析响应数据
            List<EmbeddingModelVO> models = new ArrayList<>();
            Map<String, List<Map<String, Object>>> data = response.getData();

            if (data != null) {
                for (Map.Entry<String, List<Map<String, Object>>> entry : data.entrySet()) {
                    String provider = entry.getKey();
                    List<Map<String, Object>> providerModels = entry.getValue();

                    for (Map<String, Object> modelData : providerModels) {
                        String modelType = (String) modelData.get("model_type");

                        // 只处理embedding类型的模型
                        if ("embedding".equalsIgnoreCase(modelType)) {
                            String modelName = (String) modelData.get("llm_name");
                            Boolean available = (Boolean) modelData.getOrDefault("available", false);
                            Integer maxTokens = (Integer) modelData.getOrDefault("max_tokens", 512);
                            String tags = (String) modelData.getOrDefault("tags", "");

                            EmbeddingModelVO model = EmbeddingModelVO.builder()
                                    .modelName(modelName)
                                    .provider(provider)
                                    .fullName(modelName + "@" + provider)
                                    .dimension(getDimensionByModel(modelName))
                                    .maxTokens(maxTokens)
                                    .available(available)
                                    .tags(tags)
                                    .modelType("embedding")
                                    .build();

                            models.add(model);
                        }
                    }
                }
            }

            log.info("获取到 {} 个 Embedding 模型", models.size());
            return models;

        } catch (Exception e) {
            log.error("获取 Embedding 模型列表失败", e);
            throw new BusinessException("获取 Embedding 模型列表失败: " + e.getMessage(), e);
        }
    }

    /**
     * 根据模型名称推断向量维度
     *
     * @param modelName 模型名称
     * @return 向量维度
     */
    private Integer getDimensionByModel(String modelName) {
        if (modelName == null) {
            return 768; // 默认维度
        }

        String lowerName = modelName.toLowerCase();

        // OpenAI 模型
        if (lowerName.contains("text-embedding-3-large")) {
            return 3072;
        } else if (lowerName.contains("text-embedding-3-small")) {
            return 1536;
        } else if (lowerName.contains("text-embedding-ada-002")) {
            return 1536;
        }

        // BGE 系列模型
        if (lowerName.contains("bge-large")) {
            return 1024;
        } else if (lowerName.contains("bge-base") || lowerName.contains("bge-small")) {
            return 768;
        }

        // BCE 系列模型
        if (lowerName.contains("bce-embedding")) {
            return 768;
        }

        // M3E 系列模型
        if (lowerName.contains("m3e-large")) {
            return 1024;
        } else if (lowerName.contains("m3e-base")) {
            return 768;
        }

        // Sentence Transformers
        if (lowerName.contains("all-mpnet-base")) {
            return 768;
        } else if (lowerName.contains("all-minilm")) {
            return 384;
        }

        // 默认维度
        return 768;
    }

    /**
     * 删除文档
     *
     * DELETE /api/v1/datasets/{dataset_id}/documents
     *
     * @param datasetId   知识库ID
     * @param documentIds 文档ID列表
     */
    public void deleteDocuments(String datasetId, List<String> documentIds) {
        log.info("删除文档: dataset_id={}, document_ids={}", datasetId, documentIds);

        try {
            WebClient client = createWebClient();

            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("ids", documentIds);

            RAGFlowResponse<Void> response = client.method(HttpMethod.DELETE)
                    .uri("/api/v1/datasets/" + datasetId + "/documents")
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<Void>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("删除文档失败: " + errorMsg);
            }

            log.info("文档删除成功: dataset_id={}, document_ids={}", datasetId, documentIds);

        } catch (Exception e) {
            log.error("删除文档失败: dataset_id={}, document_ids={}", datasetId, documentIds, e);
            throw new BusinessException("删除文档失败: " + e.getMessage(), e);
        }
    }

    /**
     * 更新文档配置
     *
     * PUT /api/v1/datasets/{dataset_id}/documents/{document_id}
     *
     * @param datasetId  知识库ID
     * @param documentId 文档ID
     * @param request    更新请求
     * @return 更新后的文档信息
     */
    public DocumentData updateDocument(String datasetId, String documentId, UpdateDocumentRequest request) {
        log.info("更新文档配置: dataset_id={}, document_id={}, request={}", datasetId, documentId, request);

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<DocumentData> response = client.put()
                    .uri("/api/v1/datasets/" + datasetId + "/documents/" + documentId)
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<DocumentData>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("更新文档配置失败: " + errorMsg);
            }

            log.info("文档配置更新成功: dataset_id={}, document_id={}", datasetId, documentId);
            return response.getData();

        } catch (Exception e) {
            log.error("更新文档配置失败: dataset_id={}, document_id={}", datasetId, documentId, e);
            throw new BusinessException("更新文档配置失败: " + e.getMessage(), e);
        }
    }

    /**
     * 停止文档解析
     *
     * DELETE /api/v1/datasets/{dataset_id}/chunks
     *
     * @param datasetId   知识库ID
     * @param documentIds 文档ID列表
     */
    public void stopDocumentParsing(String datasetId, List<String> documentIds) {
        log.info("停止文档解析: dataset_id={}, document_ids={}", datasetId, documentIds);

        try {
            WebClient client = createWebClient();

            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("document_ids", documentIds);

            RAGFlowResponse<Void> response = client.method(HttpMethod.DELETE)
                    .uri("/api/v1/datasets/" + datasetId + "/chunks")
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<Void>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("停止文档解析失败: " + errorMsg);
            }

            log.info("文档解析停止成功: dataset_id={}, document_ids={}", datasetId, documentIds);

        } catch (Exception e) {
            log.error("停止文档解析失败: dataset_id={}, document_ids={}", datasetId, documentIds, e);
            throw new BusinessException("停止文档解析失败: " + e.getMessage(), e);
        }
    }

    // ==================== Chat Assistant 管理 ====================

    /**
     * 创建 Chat Assistant
     *
     * POST /api/v1/chats
     *
     * @param request 创建请求
     * @return Chat Assistant详情
     */
    public ChatAssistantDetailVO createChatAssistant(CreateChatAssistantRequest request) {
        log.info("创建 Chat Assistant: name={}", request.getName());

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<ChatAssistantDetailVO> response = client.post()
                    .uri("/api/v1/chats")
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<ChatAssistantDetailVO>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("创建 Chat Assistant 失败: " + errorMsg);
            }

            log.info("Chat Assistant 创建成功: id={}, name={}", response.getData().getId(), request.getName());
            return response.getData();

        } catch (Exception e) {
            log.error("创建 Chat Assistant 失败", e);
            throw new BusinessException("创建 Chat Assistant 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 更新 Chat Assistant 配置
     *
     * PUT /api/v1/chats/{chat_id}
     *
     * @param chatId  Chat Assistant ID
     * @param request 更新请求
     * @return 更新后的 Chat Assistant 详情
     */
    public ChatAssistantDetailVO updateChatAssistant(String chatId, UpdateChatAssistantRequest request) {
        log.info("更新 Chat Assistant 配置: chat_id={}", chatId);

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<ChatAssistantDetailVO> response = client.put()
                    .uri("/api/v1/chats/{id}", chatId)
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<ChatAssistantDetailVO>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("更新 Chat Assistant 配置失败: " + errorMsg);
            }

            log.info("Chat Assistant 配置更新成功: chat_id={}", chatId);
            return response.getData();

        } catch (Exception e) {
            log.error("更新 Chat Assistant 配置失败: chat_id={}", chatId, e);
            throw new BusinessException("更新 Chat Assistant 配置失败: " + e.getMessage(), e);
        }
    }

    /**
     * 删除 Chat Assistant
     *
     * DELETE /api/v1/chats
     *
     * @param chatIds Chat Assistant ID列表
     */
    public void deleteChatAssistants(List<String> chatIds) {
        log.info("删除 Chat Assistant: ids={}", chatIds);

        try {
            WebClient client = createWebClient();

            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("ids", chatIds);

            RAGFlowResponse<Void> response = client.method(HttpMethod.DELETE)
                    .uri("/api/v1/chats")
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<Void>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("删除 Chat Assistant 失败: " + errorMsg);
            }

            log.info("Chat Assistant 删除成功: ids={}", chatIds);

        } catch (Exception e) {
            log.error("删除 Chat Assistant 失败: ids={}", chatIds, e);
            throw new BusinessException("删除 Chat Assistant 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取 Chat Assistant 详情
     *
     * GET /api/v1/chats?id={chat_id}
     *
     * @param chatId Chat Assistant ID
     * @return Chat Assistant 详情
     */
    public ChatAssistantDetailVO getChatAssistantDetail(String chatId) {
        log.info("获取 Chat Assistant 详情: chat_id={}", chatId);

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<List<ChatAssistantDetailVO>> response = client.get()
                    .uri(uriBuilder -> uriBuilder
                            .path("/api/v1/chats")
                            .queryParam("id", chatId)
                            .build())
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<ChatAssistantDetailVO>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess() || response.getData() == null || response.getData().isEmpty()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("获取 Chat Assistant 详情失败: " + errorMsg);
            }

            ChatAssistantDetailVO detail = response.getData().get(0);
            log.info("获取 Chat Assistant 详情成功: chat_id={}, name={}", chatId, detail.getName());
            return detail;

        } catch (Exception e) {
            log.error("获取 Chat Assistant 详情失败: chat_id={}", chatId, e);
            throw new BusinessException("获取 Chat Assistant 详情失败: " + e.getMessage(), e);
        }
    }

    /**
     * 列出所有 Chat Assistant
     *
     * GET /api/v1/chats
     *
     * @param page     页码（从1开始）
     * @param pageSize 每页大小
     * @return Chat Assistant 列表
     */
    public List<ChatAssistantDetailVO> listChatAssistants(Integer page, Integer pageSize) {
        log.info("列出 Chat Assistant: page={}, pageSize={}", page, pageSize);

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<List<ChatAssistantDetailVO>> response = client.get()
                    .uri(uriBuilder -> {
                        var builder = uriBuilder.path("/api/v1/chats");
                        if (page != null) {
                            builder.queryParam("page", page);
                        }
                        if (pageSize != null) {
                            builder.queryParam("page_size", pageSize);
                        }
                        return builder.build();
                    })
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<ChatAssistantDetailVO>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("列出 Chat Assistant 失败: " + errorMsg);
            }

            List<ChatAssistantDetailVO> chats = response.getData() != null ? response.getData() : new ArrayList<>();
            log.info("列出 Chat Assistant 成功: count={}", chats.size());
            return chats;

        } catch (Exception e) {
            log.error("列出 Chat Assistant 失败", e);
            throw new BusinessException("列出 Chat Assistant 失败: " + e.getMessage(), e);
        }
    }

    // ==================== Chunk 管理 ====================

    /**
     * 列出指定文档的所有 Chunk
     *
     * GET /api/v1/datasets/{dataset_id}/documents/{document_id}/chunks
     *
     * @param datasetId  知识库ID
     * @param documentId 文档ID
     * @param keywords   关键词过滤（可选）
     * @param page       页码（从1开始）
     * @param pageSize   每页大小
     * @return Chunk列表
     */
    public List<ChunkDetailVO> listChunks(String datasetId, String documentId, String keywords, Integer page, Integer pageSize) {
        log.info("列出 Chunk: dataset_id={}, document_id={}, keywords={}, page={}, pageSize={}",
                datasetId, documentId, keywords, page, pageSize);

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<List<ChunkDetailVO>> response = client.get()
                    .uri(uriBuilder -> {
                        var builder = uriBuilder.path("/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks")
                                .build(datasetId, documentId);

                        var uriBuilder2 = uriBuilder.path("/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks");
                        if (keywords != null && !keywords.isEmpty()) {
                            uriBuilder2.queryParam("keywords", keywords);
                        }
                        if (page != null) {
                            uriBuilder2.queryParam("page", page);
                        }
                        if (pageSize != null) {
                            uriBuilder2.queryParam("page_size", pageSize);
                        }
                        return uriBuilder2.build(datasetId, documentId);
                    })
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<ChunkDetailVO>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("列出 Chunk 失败: " + errorMsg);
            }

            List<ChunkDetailVO> chunks = response.getData() != null ? response.getData() : new ArrayList<>();
            log.info("列出 Chunk 成功: count={}", chunks.size());
            return chunks;

        } catch (Exception e) {
            log.error("列出 Chunk 失败: dataset_id={}, document_id={}", datasetId, documentId, e);
            throw new BusinessException("列出 Chunk 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 删除 Chunk
     *
     * DELETE /api/v1/datasets/{dataset_id}/documents/{document_id}/chunks
     *
     * @param datasetId  知识库ID
     * @param documentId 文档ID
     * @param chunkIds   Chunk ID列表
     */
    public void deleteChunks(String datasetId, String documentId, List<String> chunkIds) {
        log.info("删除 Chunk: dataset_id={}, document_id={}, chunk_ids={}", datasetId, documentId, chunkIds);

        try {
            WebClient client = createWebClient();

            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("chunk_ids", chunkIds);

            RAGFlowResponse<Void> response = client.method(HttpMethod.DELETE)
                    .uri("/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks", datasetId, documentId)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<Void>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("删除 Chunk 失败: " + errorMsg);
            }

            log.info("Chunk 删除成功: dataset_id={}, document_id={}, count={}", datasetId, documentId, chunkIds.size());

        } catch (Exception e) {
            log.error("删除 Chunk 失败: dataset_id={}, document_id={}, chunk_ids={}", datasetId, documentId, chunkIds, e);
            throw new BusinessException("删除 Chunk 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 更新 Chunk 内容
     *
     * PUT /api/v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id}
     *
     * @param datasetId  知识库ID
     * @param documentId 文档ID
     * @param chunkId    Chunk ID
     * @param request    更新请求
     * @return 更新后的 Chunk 详情
     */
    public ChunkDetailVO updateChunk(String datasetId, String documentId, String chunkId, UpdateChunkRequest request) {
        log.info("更新 Chunk 内容: dataset_id={}, document_id={}, chunk_id={}", datasetId, documentId, chunkId);

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<ChunkDetailVO> response = client.put()
                    .uri("/api/v1/datasets/{dataset_id}/documents/{document_id}/chunks/{chunk_id}",
                            datasetId, documentId, chunkId)
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<ChunkDetailVO>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("更新 Chunk 内容失败: " + errorMsg);
            }

            log.info("Chunk 内容更新成功: chunk_id={}", chunkId);
            return response.getData();

        } catch (Exception e) {
            log.error("更新 Chunk 内容失败: dataset_id={}, document_id={}, chunk_id={}",
                    datasetId, documentId, chunkId, e);
            throw new BusinessException("更新 Chunk 内容失败: " + e.getMessage(), e);
        }
    }

    /**
     * 检索 Chunk（测试向量检索效果）
     *
     * POST /api/v1/retrieval
     *
     * @param request 检索请求
     * @return 检索结果列表
     */
    public List<ChunkRetrievalResultVO> retrieveChunks(ChunkRetrievalRequest request) {
        log.info("检索 Chunk: question={}, dataset_ids={}", request.getQuestion(), request.getDatasetIds());

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<List<ChunkRetrievalResultVO>> response = client.post()
                    .uri("/api/v1/retrieval")
                    .bodyValue(request)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<ChunkRetrievalResultVO>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("检索 Chunk 失败: " + errorMsg);
            }

            List<ChunkRetrievalResultVO> results = response.getData() != null ? response.getData() : new ArrayList<>();
            log.info("检索 Chunk 成功: count={}", results.size());
            return results;

        } catch (Exception e) {
            log.error("检索 Chunk 失败: question={}", request.getQuestion(), e);
            throw new BusinessException("检索 Chunk 失败: " + e.getMessage(), e);
        }
    }

    // ==================== 文档上传和管理 ====================

    /**
     * 上传文档到知识库（带doc_id复用机制）
     *
     * POST /api/v1/datasets/{dataset_id}/documents
     *
     * @param kbId     知识库ID
     * @param file     文件
     * @param fileMd5  文件MD5哈希（用于复用检查）
     * @return 文档ID
     */
    public String uploadDocumentWithReuse(String kbId, File file, String fileMd5) {
        log.info("上传文档到知识库（带复用检查）: dataset_id={}, file={}, md5={}", kbId, file.getName(), fileMd5);

        try {
            // 1. 检查是否已存在相同MD5的文档
            DocumentData existingDoc = findDocumentByMd5(fileMd5);

            if (existingDoc != null) {
                log.info("发现已存在的文档: doc_id={}, name={}", existingDoc.getId(), existingDoc.getName());

                // 2. 关联已存在的文档到当前知识库
                associateExistingDocument(kbId, existingDoc.getId());
                log.info("成功复用文档: doc_id={}, 关联到 kb_id={}", existingDoc.getId(), kbId);
                return existingDoc.getId();
            }

            // 3. 文档不存在，执行正常上传
            log.info("未发现已存在文档，执行正常上传");
            return uploadDocument(kbId, file);

        } catch (Exception e) {
            log.error("上传文档（带复用）失败", e);
            throw new BusinessException("上传文档失败: " + e.getMessage(), e);
        }
    }

    /**
     * 上传文档到知识库（基础方法）
     *
     * POST /api/v1/datasets/{dataset_id}/documents
     */
    public String uploadDocument(String kbId, File file) {
        log.info("上传文档到知识库: dataset_id={}, file={}", kbId, file.getName());

        try {
            WebClient client = WebClient.builder()
                    .baseUrl(config.getBaseUrl())
                    .defaultHeader("Authorization", "Bearer " + config.getApiKey())
                    .build();

            MultipartBodyBuilder builder = new MultipartBodyBuilder();
            builder.part("file", new FileSystemResource(file));
            // 添加run参数，让文档上传后自动开始解析
            builder.part("run", "1");  // 1=自动解析, 0=不解析

            // RAGFlow 返回的 data 是对象数组: {"code": 0, "data": [{"id": "...", "name": "...", ...}]}
            RAGFlowResponse<List<DocumentData>> response = client.post()
                    .uri("/api/v1/datasets/" + kbId + "/documents")
                    .contentType(MediaType.MULTIPART_FORM_DATA)
                    .body(BodyInserters.fromMultipartData(builder.build()))
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<DocumentData>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("上传文档失败: " + errorMsg);
            }

            // 从返回的文档列表中获取第一个文档的ID
            List<DocumentData> documents = response.getData();
            if (documents == null || documents.isEmpty()) {
                throw new BusinessException("上传文档失败: 未返回文档数据");
            }

            String docId = documents.get(0).getId();
            log.info("文档上传成功（已触发自动解析）: doc_id={}, name={}, status={}",
                    docId, documents.get(0).getName(), documents.get(0).getStatus());
            return docId;

        } catch (Exception e) {
            log.error("上传文档失败", e);
            throw new BusinessException("上传文档失败: " + e.getMessage(), e);
        }
    }

    /**
     * 触发文档解析（基于RAGFlow源码分析实现 - 最终修复版本）
     *
     * POST /api/v1/datasets/{dataset_id}/chunks
     *
     * 经过深入源码分析和API测试发现：
     * - /v1/document/run 端点需要session认证（前端Web使用）
     * - /api/v1/datasets/{id}/chunks 端点支持API key认证（SDK使用）
     *
     * 源码位置：api/apps/sdk/doc.py:698 parse()函数
     * 认证方式：@token_required（支持Bearer API key）
     *
     * @param kbId 知识库ID
     * @param docId 文档ID
     */
    public void runDocument(String kbId, String docId) {
        log.info("触发文档解析: dataset_id={}, doc_id={}", kbId, docId);

        try {
            WebClient client = createWebClient();

            // 构造请求体：{"document_ids": [docId]}
            // SDK端点使用document_ids参数（复数），与Web端的doc_ids不同
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("document_ids", List.of(docId));

            // 调用 RAGFlow SDK 的 /chunks 端点（触发解析）
            // 该端点会调用parse()函数，将文档解析成chunks
            RAGFlowResponse<Object> response = client.post()
                    .uri("/api/v1/datasets/" + kbId + "/chunks")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<Object>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                log.warn("触发文档解析失败: {}", errorMsg);
                return;
            }

            log.info("文档解析已触发成功: doc_id={}", docId);

        } catch (Exception e) {
            log.warn("触发文档解析异常: doc_id={}, error={}",
                    docId, e.getMessage());
            // 不抛出异常，避免影响文档上传流程
        }
    }

    /**
     * 列出知识库中的所有文档
     *
     * GET /api/v1/datasets/{dataset_id}/documents
     *
     * @param kbId 知识库ID
     * @return 文档列表
     */
    public List<DocumentData> listDocuments(String kbId) {
        log.info("列出知识库文档: dataset_id={}", kbId);

        try {
            WebClient client = createWebClient();

            RAGFlowResponse<List<DocumentData>> response = client.get()
                    .uri("/api/v1/datasets/" + kbId + "/documents")
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<DocumentData>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("列出文档失败: " + errorMsg);
            }

            List<DocumentData> documents = response.getData();
            log.info("列出文档成功: 共 {} 个文档", documents != null ? documents.size() : 0);
            return documents != null ? documents : List.of();

        } catch (Exception e) {
            log.error("列出文档失败", e);
            throw new BusinessException("列出文档失败: " + e.getMessage(), e);
        }
    }

    /**
     * 根据MD5哈希查找文档（遍历所有知识库）
     *
     * 注意：RAGFlow API 没有直接的MD5查找接口，需要遍历所有知识库查找
     *
     * @param md5Hash MD5哈希值
     * @return 找到的文档，如果不存在则返回null
     */
    public DocumentData findDocumentByMd5(String md5Hash) {
        log.info("根据MD5查找文档: md5={}", md5Hash);

        try {
            // 1. 获取所有知识库
            List<KnowledgeBaseData> knowledgeBases = listKnowledgeBases();

            // 2. 遍历每个知识库，查找匹配的文档
            for (KnowledgeBaseData kb : knowledgeBases) {
                List<DocumentData> documents = listDocuments(kb.getId());

                for (DocumentData doc : documents) {
                    if (md5Hash.equals(doc.getMd5Hash())) {
                        log.info("找到匹配文档: doc_id={}, name={}, kb_id={}",
                                doc.getId(), doc.getName(), kb.getId());
                        return doc;
                    }
                }
            }

            log.info("未找到MD5匹配的文档");
            return null;

        } catch (Exception e) {
            log.warn("查找文档失败（将视为不存在）: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 关联已存在的文档到新知识库（doc_id复用）
     *
     * POST /api/v1/datasets/{dataset_id}/documents
     * Body: {"doc_id": "existing_doc_id"}
     *
     * @param targetKbId     目标知识库ID
     * @param existingDocId  已存在的文档ID
     */
    public void associateExistingDocument(String targetKbId, String existingDocId) {
        log.info("关联已存在文档: target_kb={}, doc_id={}", targetKbId, existingDocId);

        try {
            WebClient client = createWebClient();

            // 构造请求体：只传doc_id，不上传文件
            String requestBody = String.format("{\"doc_id\": \"%s\"}", existingDocId);

            RAGFlowResponse<List<DocumentData>> response = client.post()
                    .uri("/api/v1/datasets/" + targetKbId + "/documents")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<List<DocumentData>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("关联文档失败: " + errorMsg);
            }

            log.info("文档关联成功: doc_id={} 已关联到 kb_id={}", existingDocId, targetKbId);

        } catch (Exception e) {
            log.error("关联文档失败", e);
            throw new BusinessException("关联文档失败: " + e.getMessage(), e);
        }
    }

    /**
     * 创建Chat Assistant（聊天助手）
     *
     * POST /api/v1/chats
     *
     * @param name 助手名称
     * @param datasetIds 关联的知识库ID列表
     * @return Chat Assistant ID
     */
    public String createChatAssistant(String name, List<String> datasetIds) {
        log.info("创建Chat Assistant: name={}, datasetIds={}", name, datasetIds);

        try {
            WebClient client = createWebClient();

            // 构造请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("name", name);
            requestBody.put("dataset_ids", datasetIds);

            // LLM参数配置 - 只设置参数，不设置model_name
            // 根据RAGFlow源码(sdk/chat.py:86-87)，不设置llm_id时会自动使用tenant.llm_id
            // 这样确保使用Web UI中已配置的有效LLM模型，避免llm_id无效导致只返回opener的问题
            Map<String, Object> llmSetting = new HashMap<>();
            llmSetting.put("temperature", config.getLlm().getTemperature());
            llmSetting.put("top_p", config.getLlm().getTopP());
            llmSetting.put("max_tokens", config.getLlm().getMaxTokens());
            requestBody.put("llm_setting", llmSetting);

            // Prompt配置 - 使用RAGFlow标准字段名(根据dialog_app.py:65)
            Map<String, Object> promptConfig = new HashMap<>();
            promptConfig.put("similarity_threshold", 0.2);
            promptConfig.put("keywords_similarity_weight", 0.7);
            promptConfig.put("top_n", 8);
            // system字段：主提示词（原prompt字段）
            promptConfig.put("system", "你是一个智能助手。请根据知识库内容总结回答问题。" +
                    "请详细列举知识库中的数据并进行详细解答。" +
                    "当所有知识库内容都与问题无关时，你的回答必须包含这句话：\"知识库中未找到相关答案！\"。" +
                    "回答时需要考虑对话历史。\n" +
                    "以下是知识库内容：\n{knowledge}\n以上就是知识库内容。");
            // prologue字段：开场白（原opener字段）
            promptConfig.put("prologue", "你好！我是智能助手，有什么可以帮到你的？");
            promptConfig.put("empty_response", "抱歉！知识库中没有找到相关内容！");
            // parameters字段：定义knowledge变量（必需）
            List<Map<String, Object>> parameters = new ArrayList<>();
            Map<String, Object> knowledgeParam = new HashMap<>();
            knowledgeParam.put("key", "knowledge");
            knowledgeParam.put("optional", false);
            parameters.add(knowledgeParam);
            promptConfig.put("parameters", parameters);
            requestBody.put("prompt_config", promptConfig);

            RAGFlowResponse<Map<String, Object>> response = client.post()
                    .uri("/api/v1/chats")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(new org.springframework.core.ParameterizedTypeReference<RAGFlowResponse<Map<String, Object>>>() {})
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            if (response == null || !response.isSuccess()) {
                String errorMsg = response != null ? response.getMessage() : "未知错误";
                throw new BusinessException("创建Chat Assistant失败: " + errorMsg);
            }

            String chatId = (String) response.getData().get("id");
            log.info("Chat Assistant创建成功: chatId={}", chatId);
            return chatId;

        } catch (Exception e) {
            log.error("创建Chat Assistant失败", e);
            throw new BusinessException("创建Chat Assistant失败: " + e.getMessage(), e);
        }
    }

    /**
     * 创建对话（已废弃，使用createChatAssistant）
     *
     * @deprecated 使用 createChatAssistant 替代
     * POST /api/v1/conversations
     *
     * @param kbId 知识库ID
     * @return 对话ID
     */
    @Deprecated
    private String createConversation(String kbId) {
        log.info("创建RAGFlow对话: kbId={}", kbId);

        try {
            WebClient client = createWebClient();

            // 构造请求体：创建对话并关联到知识库
            String requestBody = String.format("{\"dataset_ids\": [\"%s\"], \"name\": \"API Chat\"}", kbId);

            String response = client.post()
                    .uri("/api/v1/conversations")
                    .contentType(org.springframework.http.MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(String.class)
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            log.info("创建对话响应: {}", response);

            // 提取对话ID
            if (response != null && response.contains("\"id\"")) {
                int start = response.indexOf("\"id\"") + 6;
                int end = response.indexOf("\"", start + 1);
                if (end > start) {
                    String conversationId = response.substring(start, end);
                    log.info("对话创建成功: conversationId={}", conversationId);
                    return conversationId;
                }
            }

            throw new BusinessException("创建对话失败：无法解析对话ID");

        } catch (Exception e) {
            log.error("创建对话失败", e);
            throw new BusinessException("创建对话失败: " + e.getMessage(), e);
        }
    }

    /**
     * AI问答（检索增强）- 非流式
     *
     * POST /api/v1/conversations/{conversation_id}/completions
     *
     * @param kbId     知识库ID
     * @param question 用户问题
     * @return AI答案
     * @deprecated 已废弃：使用 Conversation API，应改用 chatWithDocFilterEnhanced() 方法（使用 Chat API）
     */
    @Deprecated
    public String chat(String kbId, String question) {
        log.info("RAGFlow AI问答（非流式）: kbId={}, question={}", kbId, question);

        try {
            // 1. 创建对话（路径A简化：每次问答都创建新对话）
            String conversationId = createConversationWithRetry(kbId);

            // 2. 发送问题（带重试机制）
            WebClient client = createWebClient();

            // 构造请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("question", question);
            requestBody.put("stream", false);

            // 调用RAGFlow API（带重试）
            String response = client.post()
                    .uri("/api/v1/conversations/" + conversationId + "/completions")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(String.class)
                    .retryWhen(createRetrySpec("AI问答"))
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            // 解析响应
            log.info("RAGFlow响应: {}", response);

            // 提取answer字段（假设响应格式为：{"code": 0, "data": {"answer": "..."}）
            if (response != null && response.contains("\"answer\"")) {
                int start = response.indexOf("\"answer\"") + 10;
                int end = response.indexOf("\"", start + 1);
                if (end > start) {
                    return response.substring(start, end);
                }
            }

            return "AI答案解析失败";

        } catch (Exception e) {
            log.error("RAGFlow问答失败", e);
            throw new BusinessException("AI问答失败: " + e.getMessage(), e);
        }
    }

    /**
     * AI问答（检索增强）- SSE流式响应
     *
     * POST /api/v1/conversations/{conversation_id}/completions
     * stream=true
     *
     * @param kbId       知识库ID
     * @param question   用户问题
     * @param docIdFilter 文档ID过滤列表（用于权限感知）
     * @return 流式响应 Flux
     * @deprecated 已废弃：使用 Conversation API，应改用 chatWithDocFilterEnhanced() 方法（使用 Chat API）
     */
    @Deprecated
    public Flux<String> chatStream(String kbId, String question, List<String> docIdFilter) {
        log.info("RAGFlow AI问答（流式）: kbId={}, question={}, docIdFilter={}",
                kbId, question, docIdFilter != null ? docIdFilter.size() : 0);

        return Flux.defer(() -> {
            try {
                // 1. 创建对话
                String conversationId = createConversationWithRetry(kbId);

                // 2. 构造请求体
                Map<String, Object> requestBody = new HashMap<>();
                requestBody.put("question", question);
                requestBody.put("stream", true);

                // 添加文档过滤（如果提供）
                if (docIdFilter != null && !docIdFilter.isEmpty()) {
                    requestBody.put("doc_ids", docIdFilter);
                }

                // 3. 发送SSE流式请求
                WebClient client = createWebClient();

                return client.post()
                        .uri("/api/v1/conversations/" + conversationId + "/completions")
                        .contentType(MediaType.APPLICATION_JSON)
                        .bodyValue(requestBody)
                        .accept(MediaType.TEXT_EVENT_STREAM)
                        .retrieve()
                        .bodyToFlux(String.class)
                        .retryWhen(createRetrySpec("流式问答"))
                        .timeout(Duration.ofMillis(config.getTimeout() * 2)) // 流式响应超时加倍
                        .doOnNext(chunk -> log.debug("收到流式数据块: {}", chunk))
                        .doOnError(e -> log.error("流式响应错误", e))
                        .doOnComplete(() -> log.info("流式响应完成"));

            } catch (Exception e) {
                log.error("创建流式问答失败", e);
                return Flux.error(new BusinessException("流式问答失败: " + e.getMessage(), e));
            }
        });
    }

    /**
     * AI问答（带文档过滤）- 非流式
     *
     * 用于权限感知的问答场景
     *
     * @param kbId       知识库ID
     * @param question   用户问题
     * @param docIdFilter 可见文档ID列表
     * @return AI答案
     * @deprecated 已废弃：使用 Conversation API，应改用 chatWithDocFilterEnhanced() 方法（使用 Chat API）
     */
    @Deprecated
    public String chatWithDocFilter(String kbId, String question, List<String> docIdFilter) {
        log.info("RAGFlow AI问答（带文档过滤）: kbId={}, question={}, docCount={}",
                kbId, question, docIdFilter != null ? docIdFilter.size() : 0);

        try {
            // 1. 创建对话
            String conversationId = createConversationWithRetry(kbId);

            // 2. 构造请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("question", question);
            requestBody.put("stream", false);

            // 添加文档过滤
            if (docIdFilter != null && !docIdFilter.isEmpty()) {
                requestBody.put("doc_ids", docIdFilter);
            }

            // 3. 发送问题
            WebClient client = createWebClient();

            String response = client.post()
                    .uri("/api/v1/conversations/" + conversationId + "/completions")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(String.class)
                    .retryWhen(createRetrySpec("文档过滤问答"))
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            // 解析响应
            log.info("RAGFlow响应: {}", response);

            if (response != null && response.contains("\"answer\"")) {
                int start = response.indexOf("\"answer\"") + 10;
                int end = response.indexOf("\"", start + 1);
                if (end > start) {
                    return response.substring(start, end);
                }
            }

            return "AI答案解析失败";

        } catch (Exception e) {
            log.error("RAGFlow问答（带过滤）失败", e);
            throw new BusinessException("AI问答失败: " + e.getMessage(), e);
        }
    }

    /**
     * 创建对话（带重试机制）
     *
     * @param kbId 知识库ID
     * @return 对话ID
     * @deprecated 已废弃：使用 Conversation API，应改用 Chat API
     */
    @Deprecated
    private String createConversationWithRetry(String kbId) {
        log.info("创建RAGFlow对话（带重试）: kbId={}", kbId);

        try {
            WebClient client = createWebClient();

            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("dataset_ids", List.of(kbId));
            requestBody.put("name", "API Chat");

            String response = client.post()
                    .uri("/api/v1/conversations")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(String.class)
                    .retryWhen(createRetrySpec("创建对话"))
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            log.info("创建对话响应: {}", response);

            // 提取对话ID
            if (response != null && response.contains("\"id\"")) {
                int start = response.indexOf("\"id\"") + 6;
                int end = response.indexOf("\"", start + 1);
                if (end > start) {
                    String conversationId = response.substring(start, end);
                    log.info("对话创建成功: conversationId={}", conversationId);
                    return conversationId;
                }
            }

            throw new BusinessException("创建对话失败：无法解析对话ID");

        } catch (Exception e) {
            log.error("创建对话失败", e);
            throw new BusinessException("创建对话失败: " + e.getMessage(), e);
        }
    }

    /**
     * 创建重试策略（指数退避）
     *
     * 配置：
     * - 最多重试 3 次
     * - 初始延迟 500ms
     * - 指数退避系数 2（500ms → 1s → 2s）
     * - 最大延迟 5s
     *
     * @param operationName 操作名称（用于日志）
     * @return Retry 策略
     */
    private Retry createRetrySpec(String operationName) {
        return Retry.backoff(3, Duration.ofMillis(500))
                .maxBackoff(Duration.ofSeconds(5))
                .doBeforeRetry(signal -> {
                    long attempt = signal.totalRetries() + 1;
                    Throwable error = signal.failure();
                    log.warn("{} 失败，第 {} 次重试: {}", operationName, attempt, error.getMessage());
                })
                .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
                    log.error("{} 重试耗尽（3次），放弃操作", operationName);
                    return new BusinessException(operationName + " 失败: 重试次数已耗尽");
                });
    }

    // ==================== P2功能：增强解析（References + Token + DocIds） ====================

    /**
     * 创建 RAGFlow Session（v3.1新增）
     *
     * 用于支持多轮对话功能。首次问答时调用此方法创建 session，
     * 后续问答复用同一个 session_id 即可实现上下文连续对话。
     *
     * RAGFlow v0.21.1 API: POST /api/v1/chats/{chat_id}/sessions
     *
     * @param chatId Chat Assistant ID
     * @param sessionName Session 名称（建议格式：用户名-范围类型-时间戳）
     * @return RAGFlow 生成的 session_id
     * @throws BusinessException 创建失败时抛出
     */
    public String createSession(String chatId, String sessionName) {
        log.info("创建 RAGFlow Session: chatId={}, sessionName={}", chatId, sessionName);

        try {
            // 构造请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("name", sessionName);

            // 发送请求
            WebClient client = createWebClient();
            String jsonResponse = client.post()
                    .uri("/api/v1/chats/" + chatId + "/sessions")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(String.class)
                    .retryWhen(createRetrySpec("创建Session"))
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            log.debug("RAGFlow 创建 Session 响应: {}", jsonResponse);

            // 解析响应，提取 session_id
            JsonNode root = objectMapper.readTree(jsonResponse);
            int code = root.path("code").asInt(-1);

            if (code != 0) {
                String message = root.path("message").asText("Unknown error");
                throw new BusinessException("RAGFlow 创建 Session 失败: " + message);
            }

            String sessionId = root.path("data").path("id").asText();
            if (sessionId == null || sessionId.isEmpty()) {
                throw new BusinessException("RAGFlow 返回的 session_id 为空");
            }

            log.info("RAGFlow Session 创建成功: sessionId={}", sessionId);
            return sessionId;

        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("创建 RAGFlow Session 失败", e);
            throw new BusinessException("创建 Session 失败: " + e.getMessage(), e);
        }
    }

    /**
     * AI问答（带文档过滤）- 增强版，返回完整响应对象
     *
     * 用于P2功能：提取references、doc_ids、token统计
     *
     * RAGFlow v0.21.1 API: POST /api/v1/chats/{chat_id}/completions
     *
     * @param chatId      Chat Assistant ID
     * @param question    用户问题
     * @param docIdFilter 可见文档ID列表（注：RAGFlow v0.21.1不支持此参数，文档过滤需在创建Assistant时指定）
     * @param sessionId   RAGFlow Session ID（v3.1新增，用于多轮对话）
     * @return 完整的RAGFlow响应数据
     */
    public com.docmgmt.mvp.dto.ragflow.RAGFlowAnswerData chatWithDocFilterEnhanced(String chatId, String question, List<String> docIdFilter, String sessionId) {
        log.info("RAGFlow AI问答（v0.21.1增强版）: chatId={}, question={}, docCount={}, sessionId={}",
                chatId, question, docIdFilter != null ? docIdFilter.size() : 0, sessionId);

        try {
            // 构造请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("question", question);
            requestBody.put("stream", false);

            // v3.1修改：传递session_id以支持多轮对话
            if (sessionId != null && !sessionId.isEmpty()) {
                requestBody.put("session_id", sessionId);
                log.info("传递 session_id 到 RAGFlow: {}", sessionId);
            } else {
                log.warn("未传递 session_id，RAGFlow 将创建新 session 但不处理问题");
            }

            // 添加文档过滤（基于RAGFlow源码分析：dialog_service.py:375）
            // RAGFlow支持doc_ids参数作为逗号分隔的字符串
            if (docIdFilter != null && !docIdFilter.isEmpty()) {
                String docIds = String.join(",", docIdFilter);
                requestBody.put("doc_ids", docIds);
                log.info("添加文档过滤: 文档数量={}, doc_ids={}", docIdFilter.size(), docIds);

                // 修复：添加验证日志，便于调试文档过滤是否生效
                if (docIdFilter.size() > 100) {
                    log.warn("文档过滤列表过大({}个)，可能影响性能", docIdFilter.size());
                }
            } else {
                log.info("未指定文档过滤，将查询所有可见文档");
            }

            // 发送问题并获取完整JSON响应
            WebClient client = createWebClient();

            String jsonResponse = client.post()
                    .uri("/api/v1/chats/" + chatId + "/completions")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(requestBody)
                    .retrieve()
                    .bodyToMono(String.class)
                    .retryWhen(createRetrySpec("增强版问答"))
                    .timeout(Duration.ofMillis(config.getTimeout()))
                    .block();

            log.debug("RAGFlow响应（原始）: {}", jsonResponse);

            // 解析为完整的DTO对象
            com.docmgmt.mvp.dto.ragflow.RAGFlowAnswerData answerData = parseResponse(jsonResponse);

            // 修复：验证返回的doc_ids是否与过滤列表一致
            if (docIdFilter != null && !docIdFilter.isEmpty()) {
                List<String> returnedDocIds = extractDocIds(answerData);
                log.info("文档过滤验证: 请求文档数={}, 返回文档数={}", docIdFilter.size(), returnedDocIds.size());

                // 如果返回的文档数为0，可能表示文档过滤不生效
                if (returnedDocIds.isEmpty() && !docIdFilter.isEmpty()) {
                    log.warn("警告：文档过滤可能不生效，请求了{}个文档但返回0个", docIdFilter.size());
                }
            }

            return answerData;

        } catch (Exception e) {
            log.error("RAGFlow增强问答失败", e);
            throw new BusinessException("AI问答失败: " + e.getMessage(), e);
        }
    }

    /**
     * 解析RAGFlow API响应JSON
     *
     * @param jsonResponse RAGFlow返回的JSON字符串
     * @return 解析后的数据对象
     */
    public com.docmgmt.mvp.dto.ragflow.RAGFlowAnswerData parseResponse(String jsonResponse) {
        try {
            com.docmgmt.mvp.dto.ragflow.RAGFlowResponse<com.docmgmt.mvp.dto.ragflow.RAGFlowAnswerData> response =
                    objectMapper.readValue(jsonResponse,
                            new TypeReference<com.docmgmt.mvp.dto.ragflow.RAGFlowResponse<com.docmgmt.mvp.dto.ragflow.RAGFlowAnswerData>>() {
                            });

            if (response.getCode() != 0) {
                throw new BusinessException("RAGFlow API错误: " + response.getMessage());
            }

            return response.getData();

        } catch (Exception e) {
            log.error("解析RAGFlow响应失败: {}", jsonResponse, e);
            throw new BusinessException("解析RAGFlow响应失败: " + e.getMessage(), e);
        }
    }

    /**
     * 提取文档引用列表（P2需求）
     *
     * @param answerData RAGFlow响应数据
     * @return 文档分块列表
     */
    public List<com.docmgmt.mvp.dto.ragflow.RAGFlowChunk> extractReferences(com.docmgmt.mvp.dto.ragflow.RAGFlowAnswerData answerData) {
        if (answerData == null || answerData.getReference() == null || answerData.getReference().getChunks() == null) {
            log.warn("RAGFlow响应中没有references数据");
            return List.of();
        }
        return answerData.getReference().getChunks();
    }

    /**
     * 提取查询的文档ID列表（P2需求）
     *
     * 从doc_aggs中提取，更简洁且去重
     *
     * @param answerData RAGFlow响应数据
     * @return 文档ID列表
     */
    public List<String> extractDocIds(com.docmgmt.mvp.dto.ragflow.RAGFlowAnswerData answerData) {
        if (answerData == null || answerData.getReference() == null || answerData.getReference().getDocAggs() == null) {
            log.warn("RAGFlow响应中没有doc_aggs数据");
            return List.of();
        }
        
        // 现在docAggs是List类型，需要遍历获取docId
        return answerData.getReference().getDocAggs().stream()
                .map(com.docmgmt.mvp.dto.ragflow.RAGFlowDocAgg::getDocId)
                .collect(java.util.stream.Collectors.toList());
    }

    /**
     * 提取Token使用统计（P2需求）
     *
     * 注意：RAGFlow不返回结构化token数据，只能从prompt文本中解析估算值
     *
     * 修复：支持多种格式的token统计解析
     *
     * @param answerData RAGFlow响应数据
     * @return Token使用量（仅completion_tokens，prompt_tokens为0）
     */
    public Integer extractTokenUsage(com.docmgmt.mvp.dto.ragflow.RAGFlowAnswerData answerData) {
        if (answerData == null || answerData.getPrompt() == null || answerData.getPrompt().isEmpty()) {
            log.warn("RAGFlow响应中没有prompt字段，无法提取token统计");
            return -1;  // 修复：返回-1表示无法解析，而不是0
        }

        String prompt = answerData.getPrompt();

        // 修复：支持多种格式的token统计解析
        // 格式1: "Generated tokens(approximately): 123"
        java.util.regex.Pattern pattern1 =
                java.util.regex.Pattern.compile("Generated tokens\\(approximately\\):\\s*(\\d+)");
        java.util.regex.Matcher matcher1 = pattern1.matcher(prompt);

        if (matcher1.find()) {
            int tokens = Integer.parseInt(matcher1.group(1));
            log.debug("从prompt提取token统计（格式1）: {}", tokens);
            return tokens;
        }

        // 格式2: "tokens: 123" 或 "Tokens: 123"
        java.util.regex.Pattern pattern2 =
                java.util.regex.Pattern.compile("[Tt]okens?:\\s*(\\d+)");
        java.util.regex.Matcher matcher2 = pattern2.matcher(prompt);

        if (matcher2.find()) {
            int tokens = Integer.parseInt(matcher2.group(1));
            log.debug("从prompt提取token统计（格式2）: {}", tokens);
            return tokens;
        }

        // 格式3: "completion_tokens: 123"
        java.util.regex.Pattern pattern3 =
                java.util.regex.Pattern.compile("completion_tokens:\\s*(\\d+)");
        java.util.regex.Matcher matcher3 = pattern3.matcher(prompt);

        if (matcher3.find()) {
            int tokens = Integer.parseInt(matcher3.group(1));
            log.debug("从prompt提取token统计（格式3）: {}", tokens);
            return tokens;
        }

        log.warn("无法从prompt提取token统计，尝试的格式均未匹配。prompt内容（前200字符）: {}",
                prompt.length() > 200 ? prompt.substring(0, 200) + "..." : prompt);
        return -1;  // 修复：返回-1表示无法解析
    }

    /**
     * 测试连接
     */
    public boolean testConnection() {
        try {
            listKnowledgeBases();
            return true;
        } catch (Exception e) {
            log.error("RAGFlow 连接测试失败", e);
            return false;
        }
    }
}
